package com.jspx.security.sm;

/**
 * SM2椭圆曲线公钥密码算法
 * SM2算法：SM2椭圆曲线公钥密码算法是我国自主设计的公钥密码算法，包括SM2-1椭圆曲线数字签名算法，SM2-2椭圆曲线密钥交换协议，
 * SM2-3椭圆曲线公钥加密算法，分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是，
 * SM2算法是基于椭圆曲线上点群离散对数难题，相对于RSA算法，256位的SM2密码强度已经比2048位的RSA密码强度要高。
 */

import java.math.BigInteger;
import java.security.SecureRandom;

import com.jspx.security.utils.EncryptUtil;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECFieldElement;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.ECFieldElement.Fp;

public class SM2 {
    // 正式参数
    public static String[] ecc_param = {
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
            "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
            "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
            "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
            "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
    };
/*
    p=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
    a=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
    b=28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
    n=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
    G x =32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
    G y =BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0
 */


    public final BigInteger ecc_p = new BigInteger(ecc_param[0], 16);
    public final BigInteger ecc_a = new BigInteger(ecc_param[1], 16);
    public final BigInteger ecc_b = new BigInteger(ecc_param[2], 16);
    public final BigInteger ecc_n = new BigInteger(ecc_param[3], 16);
    public final BigInteger ecc_gx = new BigInteger(ecc_param[4], 16);
    public final BigInteger ecc_gy = new BigInteger(ecc_param[5], 16);
    public final ECCurve ecc_curve= new ECCurve.Fp(this.ecc_p, this.ecc_a, this.ecc_b);
    public final ECPoint ecc_point_g;
    public final ECDomainParameters ecc_bc_spec;
    public final ECKeyPairGenerator ecc_key_pair_generator;
    public final ECFieldElement ecc_gx_fieldelement = new Fp(this.ecc_p, this.ecc_gx);
    public final ECFieldElement ecc_gy_fieldelement = new Fp(this.ecc_p, this.ecc_gy);

    public SM2() {
        this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement);
        this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.ecc_n);
        this.ecc_key_pair_generator = new ECKeyPairGenerator();
        this.ecc_key_pair_generator.init(new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom()));

    }

    public byte[] sm2GetZ(byte[] userId, ECPoint userKey)  {

        SM3Digest sm3 = new SM3Digest();
        int len = userId.length * 8;
        sm3.update((byte) (len >> 8 & 0xFF));
        sm3.update((byte) (len & 0xFF));
        sm3.update(userId, 0, userId.length);

        byte[] p = EncryptUtil.byteConvert32Bytes(ecc_a);
        sm3.update(p, 0, p.length);

        p = EncryptUtil.byteConvert32Bytes(ecc_b);
        sm3.update(p, 0, p.length);

        p = EncryptUtil.byteConvert32Bytes(ecc_gx);
        sm3.update(p, 0, p.length);

        p = EncryptUtil.byteConvert32Bytes(ecc_gy);
        sm3.update(p, 0, p.length);

        p = EncryptUtil.byteConvert32Bytes(userKey.getX().toBigInteger());
        sm3.update(p, 0, p.length);

        p = EncryptUtil.byteConvert32Bytes(userKey.getY().toBigInteger());
        sm3.update(p, 0, p.length);

        byte[] md = new byte[sm3.getDigestSize()];
        sm3.doFinal(md, 0);
        return md;
    }

    public void sign(byte[] md, BigInteger userD, ECPoint userKey, SM2Result sm2Result) {
        BigInteger e = EncryptUtil.byteConvertInteger(md);//new BigInteger(1, md);
        BigInteger k = null;
        ECPoint kp = null;
        BigInteger r = null;
        BigInteger s = null;
        do {
            do {
                // 正式环境
//				AsymmetricCipherKeyPair keypair = ecc_key_pair_generator.generateKeyPair();
//				ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) keypair.getPrivate();
//				ECPublicKeyParameters ecpub = (ECPublicKeyParameters) keypair.getPublic();
//				k = ecpriv.getD();
//				kp = ecpub.getQ();

                k = userD;
                kp = userKey;
                // 国密规范测试 随机数k
//				String kS = "6CB28D99385C175C94F94E934817663FC176D925DD72B727260DBAAE1FB2F96F";
//		        k = new BigInteger(kS, 16);
//		        kp = this.ecc_point_g.multiply(k);

                // r
                r = e.add(kp.getX().toBigInteger());
                r = r.mod(ecc_n);
            } while (r.equals(BigInteger.ZERO) || r.add(k).equals(ecc_n));

            // (1 + dA)~-1
            BigInteger da_1 = userD.add(BigInteger.ONE);
            da_1 = da_1.modInverse(ecc_n);

            // s
            s = r.multiply(userD);
            s = k.subtract(s).mod(ecc_n);
            s = da_1.multiply(s).mod(ecc_n);
        } while (s.equals(BigInteger.ZERO));

        sm2Result.r = r;
        sm2Result.s = s;
    }

    public void verify(byte md[], ECPoint userKey, BigInteger r, BigInteger s, SM2Result sm2Result) {
        sm2Result.R = null;
        BigInteger e = EncryptUtil.byteConvertInteger(md);//   new BigInteger(1, md);
        BigInteger t = r.add(s).mod(ecc_n);
        if (!t.equals(BigInteger.ZERO)) {
            ECPoint x1y1 = ecc_point_g.multiply(sm2Result.s);
            /*
            System.out.println("计算曲线点X0: " + x1y1.getX().toBigInteger().toString(16));
			System.out.println("计算曲线点Y0: " + x1y1.getY().toBigInteger().toString(16));
			System.out.println("");
	   	  */
            x1y1 = x1y1.add(userKey.multiply(t));
           /*
            System.out.println("计算曲线点X1: " + x1y1.getX().toBigInteger().toString(16));
			System.out.println("计算曲线点Y1: " + x1y1.getY().toBigInteger().toString(16));
			System.out.println("");
 		  */
            sm2Result.R = e.add(x1y1.getX().toBigInteger()).mod(ecc_n);
            //System.out.println("R: " + sm2Result.R.toString(16));
        }
    }
}
